package com.cupdata.zicon.thrift.config;

import java.lang.reflect.Constructor;

import org.apache.commons.lang.StringUtils;
import org.apache.thrift.TMultiplexedProcessor;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import com.cupdata.zicon.annotation.ThriftClient;
import com.cupdata.zicon.annotation.ThriftController;
import com.cupdata.zicon.thrift.aop.LoggingThriftMethodInterceptor;
import com.cupdata.zicon.thrift.aop.MetricsThriftMethodInterceptor;

@Component
@Configuration
@ConditionalOnClass({ ThriftController.class})
@ConditionalOnWebApplication
public class ThriftServerAutoConfiguration implements org.springframework.beans.factory.config.BeanPostProcessor {

	private Logger log = LoggerFactory.getLogger(ThriftServerAutoConfiguration.class);

	@Bean
	public TMultiplexedProcessor tMultiplexedProcessor() {
		return new TMultiplexedProcessor();
	}

	@Bean
	@ConditionalOnMissingBean(TProtocolFactory.class)
	TProtocolFactory thriftProtocolFactory() {
		return new TBinaryProtocol.Factory();
	}

	@Bean
	@ConditionalOnMissingBean(ThriftConfigurer.class)
	ThriftConfigurer thriftConfigurer() {
		return new DefaultThriftConfigurer();
	}

	@Bean
	@ConditionalOnMissingBean(LoggingThriftMethodInterceptor.class)
	LoggingThriftMethodInterceptor loggingThriftMethodInterceptor() {
		return new LoggingThriftMethodInterceptor();
	}

	@Autowired
	private TProtocolFactory protocolFactory;

	@Autowired
	private TMultiplexedProcessor tMultiplexedProcessor;

	@Autowired
	private ThriftConfigurer thriftConfigurer;

	public interface ThriftConfigurer {
		void configureProxyFactory(ProxyFactory proxyFactory);
	}

	public static class DefaultThriftConfigurer implements ThriftConfigurer {
		@Autowired(required = false)
		private GaugeService gaugeService;

		@Autowired
		private LoggingThriftMethodInterceptor loggingThriftMethodInterceptor;

		public void configureProxyFactory(ProxyFactory proxyFactory) {
			proxyFactory.setOptimize(true);

			if (gaugeService != null) {
				proxyFactory.addAdvice(new MetricsThriftMethodInterceptor(gaugeService));
			}
			proxyFactory.addAdvice(loggingThriftMethodInterceptor);
		}
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		Class clazz = bean.getClass();
		if (bean.getClass().isAnnotationPresent(ThriftController.class)) {
//			ThriftController annotation = bean.getClass().getDeclaredAnnotation(ThriftController.class);
			ThriftController annotation = bean.getClass().getAnnotation(ThriftController.class);
			String serviceName = annotation.value();
			if (StringUtils.isEmpty(serviceName)) {
				serviceName = beanName;
			}
			try {
				register(protocolFactory.getClass(), bean, serviceName);
			} catch (NoSuchMethodException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
		return bean;
	}

	private void register(Class<? extends TProtocolFactory> factory, Object handler, String serviceName)
			throws NoSuchMethodException, SecurityException {
		Class<?>[] handlerInterfaces = ClassUtils.getAllInterfaces(handler);

		Class ifaceClass = null;
		Class<TProcessor> processorClass = null;
		Class serviceClass = null;

		for (Class<?> handlerInterfaceClass : handlerInterfaces) {
			if (!handlerInterfaceClass.getName().endsWith("$Iface")) {
				continue;
			}

			serviceClass = handlerInterfaceClass.getDeclaringClass();

			if (serviceClass == null) {
				continue;
			}

			for (Class<?> innerClass : serviceClass.getDeclaredClasses()) {
				if (!innerClass.getName().endsWith("$Processor")) {
					continue;
				}

				if (!TProcessor.class.isAssignableFrom(innerClass)) {
					continue;
				}

				if (ifaceClass != null) {
					throw new IllegalStateException("Multiple Thrift Ifaces defined on handler");
				}

				ifaceClass = handlerInterfaceClass;
				processorClass = (Class<TProcessor>) innerClass;
				break;
			}
		}
		if (ifaceClass == null) {
			throw new IllegalStateException("No Thrift Ifaces found on handler");
		}

		handler = wrapHandler(ifaceClass, handler);

		Constructor<TProcessor> processorConstructor = processorClass.getConstructor(ifaceClass);

		TProcessor processor = BeanUtils.instantiateClass(processorConstructor, handler);

		tMultiplexedProcessor.registerProcessor(serviceName, processor);

	}

	protected <T> T wrapHandler(Class<T> interfaceClass, T handler) {
		ProxyFactory proxyFactory = new ProxyFactory(interfaceClass, new SingletonTargetSource(handler));

		thriftConfigurer.configureProxyFactory(proxyFactory);

		// TODO remove from here?
		proxyFactory.setFrozen(true);
		return (T) proxyFactory.getProxy();
	}


}
